#!/usr/bin/env perl

use Crypt::CBC;
use Digest::SHA::PurePerl qw(hmac_sha256 sha256);

sub getpass {
	open STDIN, "/dev/tty" or die "couldn't open /dev/tty: $!\n";
	system "stty raw; stty -echo;";
	
	my $password = "";
	print STDERR "Enter password: ";
	while (defined($c = getc(STDIN)) and $c ne "\n" and $c ne "\r") {
		$password .= $c;
	}

	system("stty sane");
	print "\n";
	return $password;
}

# iter is number of iterations
# keylen is length of generated key in bytes
# prf is the pseudo random function (e.g. hmac_sha1)
# returns the key.
sub pbkdf2($$$$$)
{
    my ($password, $salt, $iter, $keylen, $prf) = @_;
	my ($k, $t, $u, $ui, $i);
	$t = "";
	for ($k = 1; length($t) <  $keylen; $k++) {
		$u = $ui = &$prf($salt.pack('N', $k), $password);
		for ($i = 1; $i < $iter; $i++) {
			$ui = &$prf($ui, $password);
			$u ^= $ui;
		}
		$t .= $u;
	}
	return substr($t, 0, $keylen);
}


if (@ARGV < 2 || @ARGV > 3) {
	print STDERR "Usage: plock.pl label type [datadir]\n";
	print STDERR "Type should be 'user', 'pass' or 'snip'\n";
	print STDERR "Data directory is assumed to be in current directory by default\n";
	exit(1);
}

my $label = $ARGV[0];
my $type = $ARGV[1];
my $folder = $ARGV[2] if @ARGV > 2;
$folder = "PLock_Data" unless $folder;

opendir DIR, $folder or die "Cannot open $folder folder: $!";

# get salt
open MASTER, "<$folder/master.digest" or die "Cannot open master.digest: $!";
my $sig = "";
if (read(MASTER, $sig, 4) < 4 || $sig ne "PLK\x02") {
	die "Invalid master.digest format";
}
my $salt = "";
if (read(MASTER, $salt, 64) < 64) {
	die "Cannot read salt from master.digest file";
}
close MASTER;

# get GUID for label
my $guid = undef;
my @files = readdir DIR;
foreach my $file (@files) {
	if ($file =~ /\.label$/) {
		open FD, "<$folder/$file" or die "Cannot open file $file for reading: $!";
		my @lines = <FD>;
		my $fc = join($RS, @lines);
		close FD;

		if ($fc eq $label) {
			$file =~ /^(.+)\./;
			$guid = $1;
			last;
		}
	}
}

if (!defined($guid)) {
	die "Cannot find an entry for label \"$label\"";
}

my $filename = "$folder/$guid.$type";

open FD, "<$filename" or die "Cannot open file $filename for reading: $!";

my $pass = getpass();
my $sig = "";
read(FD, $sig, 4);
if ($sig ne "PLK\x01") {
	die "Unknown file format";
}
my $iv = "";
if (read(FD, $iv, 16) < 16) {
	die "File truncated";
}
my $crypt = "";
while (1) {
	if (read(FD, $crypt, 4096, length($crypt)) == 0) {
		last;
	}
}
if (length($crypt) < 16) {
	die "File truncated";
}

my $key1 = pbkdf2($pass, $salt, 1500, 32, \&hmac_sha256);
my $key = pbkdf2($key1, "$guid.$type", 1500, 32, \&hmac_sha256);
my $cipher = new Crypt::CBC(-key => $key, -cipher => 'Rijndael_PP', -iv => $iv,
						    -literal_key => 1, -header => 'none');
my $plain = $cipher->decrypt($crypt);

my $lenbuf = substr($plain, 0, 4);
my $len = unpack("N", $lenbuf);
if ($len < 0 || $len > length($plain)) {
	die "Incorrect password";
}
my $digest = substr($plain, 4, 32);
my $final = substr($plain, 36, $len);
my $computed = sha256($final);

if ($digest ne $computed) {
	die "Incorrect password";
}

print "$final\n";

close FD;
closedir DIR;
